// Pipeline Component Library Example
// ----------------------------------
// Colin Bowern (colinbowern@hotmail.com)
// January 2007
//
// Copyright (c) 2007 Colin Bowern
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// * The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#region References
using Microsoft.CommerceServer.Interop;
using Runtime = Microsoft.CommerceServer.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
#endregion

namespace RockstarGuys.PipelineComponents
{
    /// <summary>
    /// Represents a collection of string keys and object whose value's underlying storage container is stored
    /// as opposed to the object itself.
    /// </summary>
    /// <remarks>Keys are case-insensitive strings.</remarks>
    /// <typeparam name="T">Object type</typeparam>
    /// <typeparam name="StorageContainerType">Object's container type</typeparam>
    public class DictionaryContainer<T, StorageContainerType> : Dictionary
    {
        #region Properties
        /// <summary>
        /// Gets or sets the <see cref="T:Object"/> with the specified key.
        /// </summary>
        /// <value></value>
        /// <remarks>
        /// Setting a value to null will remove it from the dictionary.
        /// </remarks>
        public new T this[string key]
        {
            get
            {
                Helper.CheckForNullReference(key, "key");

                object value = this.InnerDictionary[key];
                StorageContainerType storageContainer = (Convert.IsDBNull(value) ? default(StorageContainerType) : (StorageContainerType)value);
                T instance = default(T);
                if (storageContainer != null)
                {
                    instance = RetrieveInstanceFromContainter(storageContainer);
                }
                return instance;
            }
            set
            {
                Helper.CheckForNullReference(key, "key");

                StorageContainerType storageContainer = default(StorageContainerType);
                if (value != null)
                {
                    storageContainer = RetrieveContainer(value);
                }
                base[key] = storageContainer;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="Object"/> with the specified key.
        /// </summary>
        /// <value></value>
        public override object this[object key]
        {
            get
            {
                CheckTypeCompatibility(key, "key", typeof(string));
                return this[(string)key];
            }
            set
            {
                CheckTypeCompatibility(key, "key", typeof(string));
                CheckTypeCompatibility(value, "value", typeof(T));
                this[(string)key] = (T)value;
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.</returns>
        public override ICollection Values
        {
            get
            {
                List<T> values = new List<T>(this.Count);
                foreach (string key in this)
                {
                    values.Add(this[key]);
                }
                return values;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="DictionaryContainer&lt;T, StorageContainerType&gt;"/> class.
        /// </summary>
        /// <remarks>
        /// The underlying store can be persisted through referencing the InnerDictionary property.
        /// </remarks>
        public DictionaryContainer() : base() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DictionaryContainer&lt;T, StorageContainerType&gt;"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <remarks>
        /// The underlying store can be persisted through referencing the InnerDictionary property.
        /// </remarks>
        [CLSCompliant(false)]
        public DictionaryContainer(Runtime.IDictionary container) : base(container) { }
        #endregion

        #region Methods
        /// <summary>
        /// Adds the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public override void Add(string key, object value)
        {
            Helper.CheckForNullReference(key, "key");
            CheckTypeCompatibility(value, "value", typeof(T));

            this[key] = (T)value;
        }

        /// <summary>
        /// Determines whether the specified value contains value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value contains value; otherwise, <c>false</c>.
        /// </returns>
        public override bool ContainsValue(object value)
        {
            if (value == null)
            {
                return false;
            }

            bool isFound = false;
            foreach (string key in this.InnerDictionary)
            {
                if (this[key].Equals(value))
                {
                    isFound = true;
                    break;
                }
            }
            return isFound;
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.ICollection"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in array at which copying begins.</param>
        /// <remarks>
        /// The order of keys is not guaranteed.
        /// </remarks>
        /// <exception cref="T:System.ArgumentNullException">array is null. </exception>
        /// <exception cref="T:System.ArgumentException">The type of the source <see cref="T:System.Collections.ICollection"></see> cannot be cast automatically to the type of the destination array. </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero. </exception>
        /// <exception cref="T:System.ArgumentException">array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"></see> is greater than the available space from index to the end of the destination array. </exception>
        public override void CopyTo(Array array, int index)
        {
            Helper.CheckForNullReference(array, "array");
            Helper.CheckRange<int>(array.Rank, "array.Rank", 1, 1);
            Helper.CheckRange<int>(index, "index", 0, array.Length);
            Helper.CheckRange<int>(this.Count, "Count", 0, array.Length - index);

            foreach (string key in this)
            {
                KeyValuePair<string, T> keyValuePair = new KeyValuePair<string, T>(key, this[key]);
                try
                {
                    array.SetValue(keyValuePair, index++);
                }
                catch (InvalidCastException)
                {
                    throw new ArgumentTypeException("array", typeof(KeyValuePair<string, T>));
                }
            }
        }


        /// <summary>
        /// Retrieves the container.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected virtual StorageContainerType RetrieveContainer(T item)
        {
            Type storageContainerType = typeof(StorageContainerType);

            string containerPropertyName = null;
            switch (storageContainerType.Name)
            {
                case "ISimpleList":
                    containerPropertyName = "InnerList";
                    break;
                case "IDictionary":
                    containerPropertyName = "InnerDictionary";
                    break;
                default:
                    throw new NotSupportedException();
            }

            StorageContainerType container = default(StorageContainerType);
            if (item != null)
            {
                container = (StorageContainerType)typeof(T).GetProperty(containerPropertyName).GetValue(item, null);
            }
            return container;
        }

        /// <summary>
        /// Retrieves the instance from containter.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <returns></returns>
        protected virtual T RetrieveInstanceFromContainter(StorageContainerType container)
        {
            T instance = default(T);
            Type concreateType = typeof(T);
            Type storageContainerType = typeof(StorageContainerType);

            Type derivedClassInterface = concreateType.GetInterface("IDerivedClass", true);
            if (derivedClassInterface != null)
            {
                string derivedClassName = null;
                switch (storageContainerType.Name)
                {
                    case "IDictionary":
                        derivedClassName = ((Microsoft.CommerceServer.Runtime.IDictionary)container)["derived_class_name"] as string;
                        break;
                    default:
                        throw new NotSupportedException();
                }
                string concreateTypeName = String.Concat(concreateType.Namespace, ".", derivedClassName);
                instance = (T)Assembly.GetExecutingAssembly().CreateInstance(concreateTypeName, true, BindingFlags.Default, null, new object[] { container }, null, null);
            }
            else
            {
                instance = (T)concreateType.GetConstructor(new Type[] { storageContainerType }).Invoke(new object[] { container });
            }

            return instance;
        }
        #endregion
    }
}
